package com.jf.identity.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jf.identity.entity.ExamBaseParam;
import com.jf.identity.entity.OrgCode;
import com.jf.identity.exception.ServiceException;
import com.jf.identity.holder.ExamBaseParamHolder;
import com.jf.identity.util.Params;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.nio.charset.Charset;
import java.util.*;

/**
 * 解析原始httpPost请求体数据：
 * 1、验证orgCode
 * 2、验证token
 * 3、将"data"内数据传递给controller
 *
 * @author FCG
 */
public class HttpPostBodyFilter extends OncePerRequestFilter {

    private Logger logger = LoggerFactory.getLogger(getClass());
    /**
     * token验证和处理类
     */
    private AccessTokenHandler accessTokenHandler;
    /**
     * orgCode验证和处理类
     */
    private OrgCodeHandler orgCodeHandler;
    /**
     * data数据验证和处理类
     */
    private JsonDataHandler jsonDataHandler;

    private ExamBaseParamHolder examBaseParamHolder;
    /**
     * 不做处理的uri
     */
    public static final String PARAM_NAME_EXCLUSIONS = "exclusions";
    /**
     * 处理的method
     */
    public static final String PARAM_NAME_METHOD = "method";
    /**
     * 处理的content_type
     */
    public static final String PARAM_NAME_CONTENT_TYPE = "contentType";
    private Set<String> excludesPattern;
    private String contextPath;

    @Override
    protected void initFilterBean() {
        ServletContext servletContext = getFilterConfig().getServletContext();
        WebApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        this.orgCodeHandler = ac.getBean(OrgCodeHandler.class);
        this.accessTokenHandler = ac.getBean(AccessTokenHandler.class);
        this.jsonDataHandler = ac.getBean(JsonDataHandler.class);
        this.examBaseParamHolder = ac.getBean(ExamBaseParamHolder.class);
        this.contextPath = getContextPath(servletContext);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        //仅处理method,content_type,uri匹配的request
        if (isExclusion(request)) {
            filterChain.doFilter(request, response);
            return;
        }
        //处理以json格式传递数据的接口
        String body = getBody(request);
        logger.debug("json入参:\r\n{}", body);
        try {
            Params.isBlank(body, "request body");
            JSONObject requestParam = JSON.parseObject(body);
            //处理data数据,封装成新的HttpServletRequest
            Object data = requestParam.get("Data");
            JSONObject dataJson;
            if (data instanceof JSONObject) {
                dataJson = (JSONObject) data;
            } else if (data instanceof String) {
                dataJson = JSON.parseObject((String) data);
            } else {
                throw new IllegalArgumentException("data数据结构异常");
            }
            dataJson = Optional.ofNullable(dataJson).orElseGet(JSONObject::new);
            //校验 orgCode orgIdenCode
            String orgCode = dataJson.getString("OrgCode");
            String orgIdenCode = dataJson.getString("OrgIdenCode");
            OrgCode orgBean = new OrgCode(orgCode, orgIdenCode);
            orgCodeHandler.handle(orgBean, request, response);
            handleExamBaseParam(dataJson);
            HttpServletRequest wrapper = jsonDataHandler.handle(dataJson, request, response);
            filterChain.doFilter(wrapper, response);
        } catch (IllegalArgumentException ex) {
            logger.info("json参数过滤异常:{}", ex.getMessage());
            //参数校验、转换失败，跳转到异常提示：
            request.setAttribute("paramEx", ex.getMessage());
            request.getRequestDispatcher("/paramEx").forward(request, response);
        } finally {
            examBaseParamHolder.remove();
        }
    }

    private void handleExamBaseParam(JSONObject dataJson) {
        //校验orgCode，并存入当前线程
        String orgCode = dataJson.getString("OrgCode");
        String orgIdenCode = dataJson.getString("OrgIdenCode");
        String examId = dataJson.getString("ExamId");
        String examSessionNum = dataJson.getString("ExamSessionNum");
        String logicExamRoomNum = dataJson.getString("LogicExamRoomNum");
        String deviceId = dataJson.getString("DeviceId");

        ExamBaseParam examBaseParam = new ExamBaseParam();
        examBaseParam.setExamId(examId);
        examBaseParam.setOrgCode(orgCode);
        if(!StringUtils.isEmpty(examSessionNum)) {
            try {
                //
                int i = Integer.parseInt(examSessionNum);
                if (i < 0) {
                    throw new ServiceException("ExamSessionNum必须大于0");
                }
                examBaseParam.setExamSessionNum(examSessionNum);
            } catch (Exception exception) {
                throw new IllegalArgumentException("ExamSessionNum必须大于0");
            }
        }
        examBaseParam.setDeviceId(deviceId);
        examBaseParam.setLogicExamRoomNum(logicExamRoomNum);
        examBaseParam.setorgIdenCode(orgIdenCode);

        examBaseParamHolder.setExamBaseParam(examBaseParam);
    }

    private String getContextPath(ServletContext context) {
        String contextPath = context.getContextPath();
        if (contextPath == null || contextPath.length() == 0) {
            contextPath = "/";
        }
        return contextPath;
    }

    /**
     * 获取httpServletRequest post请求的body数据
     *
     * @param request httpServletRequest
     * @return post请求的body数据
     */
    private String getBody(HttpServletRequest request) {
        BufferedReader br = null;
        try (ServletInputStream inputStream = request.getInputStream()) {
            Charset charset = Charset.forName(
                    Optional.ofNullable(request.getCharacterEncoding())
                            .orElse("UTF-8")
            );
            br = new BufferedReader(
                    new InputStreamReader(inputStream, charset)
            );
            StringBuilder sb = new StringBuilder();
            br.lines().forEach(sb::append);
            return sb.toString();
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
            throw new UncheckedIOException(e);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    /**
     * 判断当前请求是否在处理范围内
     *
     * @param request http请求
     * @return 是否在处理范围外
     */
    private boolean isExclusion(HttpServletRequest request) {
        String method = request.getMethod();
        String contentType = request.getContentType();
        String URI = request.getRequestURI();
        logger.debug("请求方法:{}", method);
        logger.debug("请求contentType:{}", contentType);
        logger.debug("请求URI:{}", URI);
        return !methodMatch(method) || !contentTypeMatch(contentType) || excludeURIMatch(URI);
    }

    private boolean methodMatch(String method) {
        if (method == null) {
            return false;
        }
        //拿到filterConfig
        FilterConfig filterConfig = getFilterConfig();
        String paramMethod = filterConfig.getInitParameter(PARAM_NAME_METHOD);
        return paramMethod == null || method.equalsIgnoreCase(paramMethod);
    }

    private boolean contentTypeMatch(String contentType) {
        //拿到filterConfig
        FilterConfig filterConfig = getFilterConfig();
        String paramContentType = filterConfig.getInitParameter(PARAM_NAME_CONTENT_TYPE);
        if (StringUtils.isEmpty(paramContentType)) {
            return true;
        }
        if (Objects.equals(contentType, paramContentType)) {
            return true;
        }
        //application/json;charset=utf-8 与 application/json
        int i;
        String paramTypeUpper;
        if ((i = paramContentType.indexOf(";")) != -1) {
            paramTypeUpper = paramContentType.substring(0, i).toUpperCase().trim();
        } else {
            paramTypeUpper = paramContentType.toUpperCase().trim();
        }
        int j;
        String contentTypeUpper;
        if ((j = contentType.indexOf(";")) != -1) {
            contentTypeUpper = contentType.substring(0, j).toUpperCase().trim();
        } else {
            contentTypeUpper = contentType.toUpperCase().trim();
        }

        return Objects.equals(paramTypeUpper, contentTypeUpper);
    }

    private boolean excludeURIMatch(String requestURI) {
        if (requestURI == null) {
            return false;
        }
        //拿到filterConfig
        FilterConfig filterConfig = getFilterConfig();
        String exclusions = filterConfig.getInitParameter(PARAM_NAME_EXCLUSIONS);
        if (exclusions == null || "".equals(exclusions.trim())) {
            return false;
        }
        excludesPattern = new HashSet<>(Arrays.asList(exclusions.split("\\s*,\\s*")));
        if (this.contextPath != null) {
            requestURI = requestURI.substring(this.contextPath.length());
        }
        if (!requestURI.startsWith("/")) {
            requestURI = "/" + requestURI;
        }
        for (String pattern : excludesPattern) {
            if (match(pattern, requestURI)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 检测给定字符串(source)是否匹配给定规则(pattern)
     *
     * @param pattern 匹配规则
     * @param source  匹配字符串
     * @return 是否匹配
     */
    private boolean match(String pattern, String source) {
        if (pattern == null || source == null) {
            return false;
        }
        // *.html
        if (pattern.startsWith("*")) {
            pattern = pattern.substring(1);
            if (source.length() > pattern.length()) {
                return source.endsWith(pattern);
            }
        } else if (pattern.endsWith("*")) {
            // common/*
            pattern = pattern.substring(0, pattern.length() - 1);
            if (source.length() > pattern.length()) {
                return source.startsWith(pattern);
            }
        } else if (pattern.contains("*")) {
            // common/*/index.html
            int start = pattern.indexOf("*");
            int end = pattern.lastIndexOf("*");
            String preffix = pattern.substring(0, start);
            String suffix = pattern.substring(end + 1);
            if (source.length() > (preffix.length() + suffix.length())) {
                return source.startsWith(preffix) && source.endsWith(suffix);
            }
        } else {
            // common/index.html
            return pattern.equals(source);
        }
        return false;
    }

}






















